﻿using hyjx.s7.Exceptions;
using hyjx.s7;
using Hyjx.MixerReportService.Configs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Hyjx.GF.Database;
using Microsoft.EntityFrameworkCore;
using MixerReport.Common;
using System.Diagnostics;
using MixerReport.Common.Types;

namespace Hyjx.MixerReportService
{
    public class ReportService : BackgroundService
    {
        readonly ILogger<ReportService>? _logger;
        readonly IConfiguration? _config;
        readonly MaterialConfig[]? _materials;

        int _checkDataSeconds;
        int _realtimeDataBlock;
        int _realtimeStartIndex;
        int _realtimeDataLength;
        MixerConfig[]? _mixers;

        string? _dbConnectionString;
        

        readonly ConcurrentDictionary<string, S7Protocol> _mixerProtocols = new();

        public ReportService(IConfiguration config, ILogger<ReportService> logger)
        {
            _config = config;
            _logger = logger;
            MaterialHelper materialHelper = new();
            _materials = materialHelper.GetMaterials();
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            if (_config == null)
                return Task.CompletedTask;

            _checkDataSeconds = _config.GetValue<int>("CheckDataSeconds");
            _realtimeDataBlock = _config.GetValue<int>("RealtimeDataBlock");
            _realtimeStartIndex = _config.GetValue<int>("RealtimeStartIndex");
            _realtimeDataLength = _config.GetValue<int>("RealtimeDataLength");

            IConfigurationRoot con = new ConfigurationBuilder()
                .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
                .AddJsonFile("DBsettings.json").Build();
            string? dbPath = con["GFDB:Path"];
            string? dbPassword = con["GFDB:Password"];
            if (string.IsNullOrEmpty(dbPath) || string.IsNullOrEmpty(dbPath))
                _logger?.LogError("读取数据库配置失败");
            else
                _dbConnectionString = $"Filename={dbPath};Password={dbPassword}";

            if(string.IsNullOrEmpty(_dbConnectionString))
            {
                _logger?.LogError("数据库连接字符串为空");
                return Task.CompletedTask;
            }
            using GFDbContext db = new(_dbConnectionString);
            db.Database.Migrate();

            _mixers = _config.GetSection("Mixers").Get<MixerConfig[]>();

            if (_mixers == null || !_mixers.Any(p => p.IsEnabled))
                return Task.CompletedTask;

            foreach (var item in _mixers.Where(p=>p.IsEnabled == true))
            {
                if (item.Plcs == null || !item.Plcs.Any())
                    continue;

                foreach (var plc in item.Plcs.Where(p => p.IsEnabled))
                {
                    Task.Run(() =>
                    {
                        try
                        {
                            TryAddToProtocolsAndConnect(plc, null);
                        }
                        catch (Exception ex)
                        {
                            _logger?.LogError(ex, $"构建 {plc.DeviceCode} 连接失败。");
                        }
                    }, cancellationToken);
                }
            }
            _logger?.LogInformation("搅拌机报表服务已启动。");
            return base.StartAsync(cancellationToken);
        }

        public override Task StopAsync(CancellationToken cancellationToken)
        {
            _checkDataSeconds = 0;
            _realtimeDataBlock = 0;
            _realtimeStartIndex = 0;
            _realtimeDataLength = 0;
            _mixers = null;
            _mixerProtocols.Clear();

            _logger?.LogInformation("搅拌机报表服务已停止。");
            return base.StopAsync(cancellationToken);
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            int elapsedTime = 1;

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    if (elapsedTime == _checkDataSeconds)
                    {
                        Task.Run(CollectData, stoppingToken);
                        elapsedTime = 1;
                    }
                    else
                        elapsedTime++;

                }
                catch (Exception ex)
                {
                    _logger?.LogError(ex, "服务执行出错。");
                }
                finally
                {
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                }
            }

            return Task.CompletedTask;

        }


        S7Protocol TryAddToProtocolsAndConnect(DevicePlcConfig plc, S7Protocol? protocol)
        {
            if (protocol is null)
            {
                protocol = new(plc.CpuType, plc.Ip, plc.Rack, plc.Slot);
                _mixerProtocols.TryAdd(plc.Ip, protocol);
            }

            try
            {
                if (!protocol.IsConnected)
                    protocol.Connect();
            }
            catch (S7ProtocolConnectException ex)
            {
                _logger?.LogError(ex, "构建协议并连接失败。");
            }

            return protocol;
        }

        private void CollectData()
        {
            if (_mixers == null || !_mixers.Any(p => p.IsEnabled) || string.IsNullOrEmpty(_dbConnectionString) || _materials == null)
                return;

            foreach (var item in _mixers.Where(p => p.IsEnabled == true))
            {
                if (item.Plcs == null || !item.Plcs.Any())
                    continue;

                foreach (var plc in item.Plcs.Where(p => p.IsEnabled))
                {
                    _mixerProtocols.TryGetValue(plc.Ip, out S7Protocol? protocol);
                    protocol ??= TryAddToProtocolsAndConnect(plc, null);


                    Task.Run(() =>
                    {
                        if (!protocol.IsConnected)
                        {
                            TryAddToProtocolsAndConnect(plc, null);
                            return;
                        }

                        try
                        {
                            byte[] bytes = protocol.ReadBytes(_realtimeDataBlock, _realtimeStartIndex, _realtimeDataLength);
                            short materialCode = new byte[] { bytes[50], bytes[51] }.FromBytesToShort();
                            string material = string.Empty;
                            MaterialConfig? ma = null;
                            bool isFrl = false;
                            if (plc.FeatureType == PlcFeatureTypes.Frl)
                                isFrl = true;

                            ma = _materials.FirstOrDefault(p => p.Code == materialCode && p.IsFrlMaterial == isFrl);

                            if (ma != null)
                                material = ma.Name;

                            MixerDataReportEntity entity = new()
                            {
                                ScatteredRotateSpeed = new byte[] { bytes[0], bytes[1] }.FromBytesToShort(),
                                ScatteredRotateActualSpeed = (float)Math.Round(new byte[] { bytes[2], bytes[3], bytes[4], bytes[5] }.FromBytesToFloat(), 3),
                                StirRotateSpeed = new byte[] { bytes[6], bytes[7] }.FromBytesToShort(),
                                StirRotateActualSpeed = (float)Math.Round(new byte[] { bytes[8], bytes[9], bytes[10], bytes[11] }.FromBytesToFloat(), 3),
                                ElapsedTime = new byte[] { bytes[12], bytes[13] }.FromBytesToShort(),
                                RemainTime = new byte[] { bytes[14], bytes[15] }.FromBytesToShort(),
                                ActualTemperature = (float)Math.Round(new byte[] { bytes[16], bytes[17], bytes[18], bytes[19] }.FromBytesToFloat(), 3),
                                ActualPressure = (float)Math.Round(new byte[] { bytes[20], bytes[21], bytes[22], bytes[23] }.FromBytesToFloat() / 1000, 3),
                                StirCurrent = (float)Math.Round(new byte[] { bytes[28], bytes[29], bytes[30], bytes[31] }.FromBytesToFloat(), 3),
                                ScatteredCurrent = (float)Math.Round(new byte[] { bytes[32], bytes[33], bytes[34], bytes[35] }.FromBytesToFloat(), 3),
                                Temperature = (float)Math.Round(new byte[] { bytes[36], bytes[37], bytes[38], bytes[39] }.FromBytesToFloat(), 3),
                                Pressure = (float)Math.Round(new byte[] { bytes[40], bytes[41], bytes[42], bytes[43] }.FromBytesToFloat() / 1000, 3),
                                Step = new byte[] { bytes[48], bytes[49] }.FromBytesToShort(),
                                Material = material,
                                MaterialWeight = (float)Math.Round(new byte[] { bytes[52], bytes[53], bytes[54], bytes[55] }.FromBytesToFloat(), 3),
                                MaterialActualWeight = (float)Math.Round(new byte[] { bytes[56], bytes[57], bytes[58], bytes[59] }.FromBytesToFloat(), 3),
                                 ReturnWaterTemperature = (float)Math.Round(new byte[] { bytes[66], bytes[67], bytes[68], bytes[69] }.FromBytesToFloat(), 3),
                                DeviceName = plc.DeviceName,
                                DeviceCode = plc.DeviceCode,
                                RecordTime = DateTime.Now
                            };

                            // 对某些负数上报，做出置 0 处理
                            if (entity.RemainTime < 0)
                                entity.RemainTime = 0;

                            using GFDbContext context = new(_dbConnectionString);
                            context.MixerDataReports.Add(entity);
                            context.SaveChanges();
                        }
                        catch(DbUpdateException ex)
                        {
                            _logger?.LogError(ex, "采集数据入库出错。");
                        }
                        catch(S7ProtocolReadBytesException ex)
                        {
                            _logger?.LogError(ex, "采集数据读取 PLC 出错。");
                        }
                        catch (Exception ex)
                        {
                            _logger?.LogError(ex, "采集数据出错。");
                        }
                    });
                }
            }
        }

    }
}
